/*
OpenMVL Matrix Vector Library
Copyright (c) 2009 FluidInteractive (R)
 
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
 
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
 
Written by: Nicola Candussi <nicola@fluidinteractive.com>
*/

//element_binary.hpp

#ifndef MVL_ELEMENT_BINARY_HPP
#define MVL_ELEMENT_BINARY_HPP

#include "layout_element_binary.hpp"
#include "promote_traits.hpp"

namespace mvl {

template<typename E1, typename E2, typename Fn, typename TagFn>
struct element_binary {

    typedef E1 expr1_t;
    typedef typename expr1_t::layout_t          layout1_t;
    //the order is chosen to be the first operand's
    typedef typename layout1_t::order           order;
    //so make sure the second operand has the same order
    typedef const expr_order<order, const E2>   expr2_t;

    typedef typename expr2_t::layout_t          layout2_t;

    typedef typename layout::layout_element_binary<
        layout1_t,
        layout2_t,
        TagFn
    >::type layout_t;

    typedef typename expr1_t::const_closure_t expr1_closure_t;
    typedef typename expr2_t::const_closure_t expr2_closure_t;

    typedef typename promote_traits<
        typename expr1_t::value_type,
        typename expr2_t::value_type
    >::promote_t value_type;


    template<typename Entry>
    static value_type at(expr1_t const& e1, expr2_t const& e2) {
        return Fn::template apply<Entry>(e1, e2, typename Entry::src1::tag(), typename Entry::src2::tag());
    }
};

//add
struct tagfn_add {
    template<typename Tag1, typename Tag2>
    struct apply : layout::add_tags<Tag1, Tag2> { };
};

struct fn_add {
    
    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_v_, layout::_v_) {
        return e1.template at<typename Entry::src1>() 
               + e2.template at<typename Entry::src2>();
    } 

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_v_, layout::_1_) {
        typedef typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t value_type;
        return e1.template at<typename Entry::src1>() + value_type(1); 
    }

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_1_, layout::_v_) {
        typedef typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t value_type;
        return value_type(1) + e2.template at<typename Entry::src2>(); 
    } 
    
    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_v_, layout::_0_) {
        return e1.template at<typename Entry::src1>();
    }

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_0_, layout::_v_) {
        return e2.template at<typename Entry::src2>(); 
    };

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_1_, layout::_1_) {
        typedef typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t value_type;
        return value_type(2);
    } 
};


//sub
struct tagfn_sub {
    template<typename Tag1, typename Tag2>
    struct apply : layout::sub_tags<Tag1, Tag2> { };
};

struct fn_sub {

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_v_, layout::_v_) {
        return e1.template at<typename Entry::src1>() 
               - e2.template at<typename Entry::src2>();
    } 

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_v_, layout::_1_) {
        typedef typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t value_type;
        return e1.template at<typename Entry::src1>() - value_type(1);
    } 

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_1_, layout::_v_) {
        typedef typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t value_type;
        return value_type(1) - e2.template at<typename Entry::src2>(); 
    } 

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_v_, layout::_0_) {
        return e1.template at<typename Entry::src1>(); 
    }

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_0_, layout::_v_) {
        return e2.template at<typename Entry::src2>();
    } 
};

//mul
struct tagfn_mul {
    template<typename Tag1, typename Tag2>
    struct apply : layout::mul_tags<Tag1, Tag2> { };
};

struct fn_mul {

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_v_, layout::_v_) {
        return e1.template at<typename Entry::src1>() 
               * e2.template at<typename Entry::src2>();
    }

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_v_, layout::_1_) {
        return e1.template at<typename Entry::src1>();
    } 
    
    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_1_, layout::_v_) {
        return e2.template at<typename Entry::src2>(); 
    } 
};

//div
struct tagfn_div {
    template<typename Tag1, typename Tag2>
    struct apply : layout::div_tags<Tag1, Tag2> { };
};

struct fn_div {

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_v_, layout::_v_) {
        return e1.template at<typename Entry::src1>() 
              / e2.template at<typename Entry::src2>();
    }

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_v_, layout::_1_) {
        return e1.template at<typename Entry::src1>(); 
    }

    template<typename Entry, typename E1, typename E2>
    static typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
    apply(E1 const& e1, E2 const& e2, layout::_1_, layout::_v_) {
        typedef typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t value_type;
        return value_type(1) / e2.template at<typename Entry::src2>(); 
    } 
};

} // namespace mvl

#endif
